﻿using System;
using System.Collections.Generic;
using System.Linq;
using Cards;

namespace GoFishGame
{
    /// <summary>
    ///     Summary description for Game
    /// </summary>
    public class Game
    {
        private readonly Dictionary<CardRank, Player> _books;
        private readonly Deck _deck;
        private readonly List<Player> _players;

        private int _gameID;

        public bool IsGameOver { get; private set; }
        public int GameID { get; private set; }

        public Game(string player, int gameId)
        {
            _players = new List<Player>();
            //foreach (var player in players)
                _players.Add(new Player(player));
                GameID = gameId;

            _books = new Dictionary<CardRank, Player>();

            _deck = new Deck();
            _deck.Shuffle();
        }

        public void JoinGame(string player)
        {
            _players.Add(new Player(player));
        }

        public void StartGame()
        {
            Deal();

            foreach (var player in _players)
                PullOutBooks(player);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestedRank"></param>
        /// <param name="askingPlayerName"></param>
        /// <param name="askedPlayerName"></param>
        /// <param name="gameID"></param>
        /// <returns></returns>
        /// <exception cref="">If requestedRank is not valid, if either playername is invalid</exception>
        public bool AskForCard(int requestedRank, string askingPlayerName, string askedPlayerName, int gameID)
        {
            var askingPlayer = GetPlayer(askingPlayerName);
            var askedPlayer = GetPlayer(askedPlayerName);

            var rank = (CardRank)requestedRank;
            
            var cardsToTake = askedPlayer.TakeCards(rank).ToList();
            var receivedCards = cardsToTake.Any();

            if (receivedCards)
            {
                askingPlayer.AddCardsToHand(cardsToTake);
                PullOutBooks(askingPlayer);
                CheckForEndGame();
                //LogMove(gameID, askedPlayer.Name + " gave " + cardsToTake.Count() + " " + rank.ToString()+ " to " + askingPlayer.Name);
            }
            else
            {
               // LogMove(gameID, askedPlayer.Name + " had no " + rank.ToString() + ". " + askingPlayer.Name + " had to go fish.");
            }

            return receivedCards;
        }

        /// <summary>
        /// Takes the selected card from the pile into the players hand
        /// </summary>
        /// <param name="cardIndex"></param>
        /// <param name="playerName"></param>
        /// <exception cref="">If card index is not within the range of the pile, if playername is invalid</exception>
        public void GoFish(int cardIndex, string playerName)
        {
            var askingPlayer = GetPlayer(playerName);
            askingPlayer.AddCardToHand(_deck.GetCard(cardIndex)); // Throws exception if index is out of range
            PullOutBooks(askingPlayer);
        }

        /// <summary>
        /// If the game is over, determines the player which the most books.
        /// </summary>
        /// <returns>Player with the most books.  Null if the game is not over</returns>
        public Player EvaluateGame()
        {
            if (!IsGameOver)
                return null;

            // TODO need to figure out linq for this crap :(
            var numberOfBooks = new Dictionary<string, int>();
            foreach (var player in _books.Values)
            {
                if (numberOfBooks.ContainsKey(player.Name))
                {
                    numberOfBooks[player.Name] += 1;
                }
                else
                {
                    numberOfBooks.Add(player.Name, 1);
                }
            }

            var playerName = numberOfBooks.OrderByDescending(p => p.Value).Select(p => p.Key).First();
            return GetPlayer(playerName);
        }

        public List<Card> GetPlayerCardNames(string playerName)
        {
            var player = GetPlayer(playerName);
            return player.Hand.OrderBy(x => x.Rank).ToList();
        }

        public string ListCurrentBooks()
        {
            var whoHasWhichBooks = "";

            foreach (var rank in _books.Keys)
                whoHasWhichBooks += _books[rank].Name + " has a book of "
                                    + Card.Plural(rank) + Environment.NewLine;
            return whoHasWhichBooks;
        }

        /// <summary>
        /// Deals five cards to each player
        /// </summary>
        private void Deal()
        {
            for (var i = 0; i < 5; i++)
            {
                foreach (var player in _players)
                    player.AddCardToHand(_deck.DealCard());
            }
        }

        private void CheckForEndGame()
        {
            if (_deck.IsEmpty)
            {
                IsGameOver = true;
                return;
            }

            if (_players.Any(player => !player.Hand.Any()))
            {
                IsGameOver = true;
            }
        }

        /// <summary>
        /// Checks if card user is asking for is actually in their hand
        /// </summary>
        /// <param name="card"></param>
        /// <param name="player"></param>
        /// <returns></returns>
        public bool CheckIfCardIsInHand(int requestedRank, string askingPlayerName)
        {
            var askingPlayer = GetPlayer(askingPlayerName);
            var rank = (CardRank)requestedRank;

            return askingPlayer.Hand.Where(c => c.Rank == rank).Any();
        }

        public bool CheckIfValidPlayer(string requestedPlayer, string askingPlayerName)
        {
            var askingPlayer = GetPlayer(askingPlayerName);
            var askedPlayer = GetPlayer(requestedPlayer);

            return askedPlayer != null;
        }

        /// <summary>
        /// Checks the player's hand for books
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        /// <exception cref="">If player is invalid</exception>
        private void PullOutBooks(Player player)
        {
            var booksPulled = player.PullOutBooks();

            foreach (var rank in booksPulled)
                _books.Add(rank, player);
        }

        private Player GetPlayer(string playerName)
        {
            var player = _players.FirstOrDefault(x => x.Name == playerName);
            //if (player == null) throw new Exception(); //TODO illegal argument?
            return player;
        }

        public int GetPlayerCount()
        {
            return _players.Count();
        }
    }
}